use crate::{
    cmd, driver,
    error::AppErr,
    task::handler_task::{self, Response},
};
use cfw_kit::ota;
use cmsis_rtos2_rs::{
    osWaitForever, os_delay,
    static_mem::{EventFlags, MutexValue},
};
use periph_utils::stm32f1::stm32f103::SCB;
use proto_bytes::{
    frame::{Proto, Res2},
    message::MessageRef,
    package::{poll_ack, ty},
    proto2::ToProtoBytes2,
    ProtoEncode2,
};
use seq_map::SeqMap;
use utils::log;
pub mod can_conn;
mod seq_map;

const FLAG_RES_ACK: u32 = 0x01;
const FLAG_RES_LOCK: u32 = 0x02;

struct Conn {
    flags: EventFlags,
    seq_map: MutexValue<SeqMap<3>>,
}

impl Conn {
    const fn uninit() -> Self {
        Self {
            flags: EventFlags::uninit(),
            seq_map: MutexValue::uninit(SeqMap::const_new()),
        }
    }
    unsafe fn init_once(&self) {
        self.flags.init_once("conn");
        self.seq_map.init_once("conn");
        self.flags.set_flags(FLAG_RES_LOCK);
    }
}

static CONN: Conn = Conn::uninit();

pub unsafe fn init() {
    CONN.init_once();
}

pub fn write<T: ToProtoBytes2>(x: T) {
    #[cfg(feature = "cp_drop")]
    driver::cmd_uart::write(x);

    #[cfg(not(feature = "cp_drop"))]
    driver::cmd_can::write(x);
}

pub type ProtoRes<T> = Proto<Res2<T, AppErr>>;

pub fn write_wait_ack<T: ProtoEncode2 + Copy>(x: ProtoRes<T>) {
    _ = CONN.flags.wait_any_flags(FLAG_RES_LOCK, osWaitForever);

    CONN.flags.clear_flags(FLAG_RES_ACK);
    for _ in 0..3 {
        write(x);
        if CONN.flags.wait_any_flags(FLAG_RES_ACK, 500).is_ok() {
            break;
        }
    }

    _ = CONN.flags.set_flags(FLAG_RES_LOCK);

    CONN.seq_map.lock().clear(x.seq);
}

pub fn dispath(req: MessageRef) {
    let ty = req.ty();
    match ty {
        ty::SIMPLE_REQ => on_simple_req(req),
        ty::REQ => on_req(req),
        ty::RES_ACK => on_res_ack(req),
        ty::CONNECT => on_connect(req),
        ty::PING => on_ping(req),
        ty::POLL => on_poll(req),
        _ => {}
    }
}

fn on_poll(req: MessageRef) {
    let body = if let Some(f) = CONN.seq_map.lock().flag(req.seq()) {
        f.flag
    } else {
        poll_ack::FINISH
    };
    write(Proto {
        ty: ty::POLL_ACK,
        seq: req.seq(),
        src: req.dst(),
        dst: req.src(),
        body,
    });
}

fn on_connect(req: MessageRef) {
    let mut seq_map = CONN.seq_map.lock();

    if let Some(conn_flag) = seq_map.flag(req.seq()) {
        if conn_flag.flag == poll_ack::EXEC {
            log!("connect fail2");
            return;
        }
    } else {
        if let Some(conn_flag) = seq_map.free() {
            conn_flag.seq = req.seq();
            conn_flag.flag = poll_ack::FINISH;
        } else {
            log!("connect fail");
            return;
        }
    }

    write(Proto {
        ty: ty::CONNECT_ACK,
        seq: req.seq(),
        src: req.dst(),
        dst: req.src(),
        body: (),
    });
}

fn on_ping(req: MessageRef) {
    write(Proto {
        ty: ty::PONG,
        seq: req.seq(),
        src: req.dst(),
        dst: req.src(),
        body: (),
    });
}

fn on_req(req: MessageRef) {
    if !req.is_valid_cmd() {
        return;
    }
    let req = req.into_req();

    write(Proto {
        ty: ty::REQ_ACK,
        seq: req.seq(),
        src: req.dst(),
        dst: req.src(),
        body: (),
    });

    let mut seq_map = CONN.seq_map.lock();
    if let Some(conn_flag) = seq_map.flag(req.seq()) {
        if conn_flag.flag == poll_ack::EXEC {
            return;
        }
        conn_flag.flag = poll_ack::EXEC;

        #[cfg(feature = "cp_heat")]
        {
            use crate::cp_heat::request_task;
            if request_task::post(req) {
                return;
            }
        }

        handler_task::post(req.to_owned());
    }
}

fn on_simple_req(msg: MessageRef) {
    if !msg.is_valid_cmd() {
        return;
    }

    let req = msg.into_req();
    let cmd = req.cmd();

    let ret = match cmd {
        cmd::OTA_START | cmd::BOOT_OTA_START => req.parse().ok_or(AppErr::Parse).and_then(|req| {
            ota::on_start(req)?;
            Ok(Response::Empty(()))
        }),

        cmd::OTA_TRANSLATE | cmd::BOOT_OTA_TRANSLATE => {
            req.parse().ok_or(AppErr::Parse).and_then(|req| {
                ota::on_translate(req)?;
                Ok(Response::Empty(()))
            })
        }

        cmd::OTA_COMPLETE => req.parse().ok_or(AppErr::Parse).and_then(|req| {
            ota::on_complete(req)?;
            Ok(Response::Empty(()))
        }),

        cmd::BOOT_OTA_COMPLETE => req.parse().ok_or(AppErr::Parse).and_then(|req| {
            ota::on_boot_complete(req)?;
            Ok(Response::Empty(()))
        }),

        #[cfg(feature = "cp_drop")]
        _ => crate::cp_drop::handler::on_simple_req(req),

        #[cfg(feature = "cp_cargo")]
        _ => crate::cp_cargo::handler::on_simple_req(req),

        #[cfg(feature = "cp_door")]
        _ => crate::cp_door::handler::on_simple_req(req),

        #[cfg(feature = "cp_heat")]
        _ => crate::cp_heat::handler::on_simple_req(req),

        #[cfg(feature = "cp_weight")]
        _ => crate::cp_weight::handler::on_simple_req(req),
    };

    if let Err(e) = ret {
        log!("exec:{:?}", e);
    }

    write(Proto {
        ty: ty::SIMPLE_RES,
        seq: req.seq(),
        src: req.dst(),
        dst: req.src(),
        body: Res2 { cmd, ret },
    });

    if ((cmd == cmd::OTA_COMPLETE) || (cmd == cmd::BOOT_OTA_COMPLETE)) && ret.is_ok() {
        os_delay(200);
        SCB::sys_reset()
        // unsafe { boot2(BOOTLOADER_ADDR); }
    }
}

fn on_res_ack(_req: MessageRef) {
    CONN.flags.set_flags(FLAG_RES_ACK);
}
